#include <stdlib.h>
#include <pthread.h>
#include "resource_pool.h"

#define MEMPOOL_BUG_ON(exp) do {} while(0);

static void add_element(resource_pool_t *pool, void *element)
{
	MEMPOOL_BUG_ON(pool->curr_nr >= pool->min_nr);
	pool->elements[pool->curr_nr++] = element;
}

static void *remove_element(resource_pool_t *pool)
{
	MEMPOOL_BUG_ON(pool->curr_nr <= 0);
	return pool->elements[--pool->curr_nr];
}

static void free_pool(resource_pool_t *pool)
{
	while (pool->curr_nr) {
		void *element = remove_element(pool);
		pool->free(element, pool->pool_data);
	}

	pthread_mutex_destroy(&pool->lock);
	pthread_cond_destroy(&pool->has_free_elements);
	free(pool->elements);
	free(pool);
}

resource_pool_t *resource_pool_create(int min_nr, resource_pool_alloc_t *alloc_fn, resource_pool_free_t *free_fn, void *pool_data)
{
	resource_pool_t *pool = NULL;
	pool = malloc(sizeof(*pool));
	if (!pool) {
		return NULL;
	}

	pool->min_nr = min_nr;
	pool->curr_nr = 0;

	pool->elements = malloc(min_nr * sizeof(void *));
	if (!pool->elements) {
		free(pool);
		return NULL;
	}

	pthread_mutex_init(&pool->lock, NULL);
	pthread_cond_init(&pool->has_free_elements, NULL);
	pool->alloc = alloc_fn;
	pool->free = free_fn;
	pool->pool_data = pool_data;

	/*
	 * First pre-allocate the guaranteed number of buffers.
	 */
	while (pool->curr_nr < pool->min_nr) {
		void *element = NULL;

		element = pool->alloc(pool->pool_data);
		if (!element) {
			free_pool(pool);
			return NULL;
		}
		add_element(pool, element);
	}
	return pool;
}

void resource_pool_destroy(resource_pool_t *pool)
{
	/* Check for outstanding elements */
	MEMPOOL_BUG_ON(pool->curr_nr != pool->min_nr);
	free_pool(pool);
}

void *resource_pool_alloc(resource_pool_t *pool)
{
	void *element = NULL;

	element = pool->alloc(pool->pool_data);
	if (element) {
		return element;
	}

	pthread_mutex_lock(&pool->lock);

	while (pool->curr_nr == 0) {
		pthread_cond_wait(&pool->has_free_elements, &pool->lock);
	}

	element = remove_element(pool);
	
	pthread_mutex_unlock(&pool->lock);

	return element;
}

void resource_pool_free(resource_pool_t *pool, void *element)
{
	if (!element) {
		return;
	}

	pthread_mutex_lock(&pool->lock);

	if (pool->curr_nr < pool->min_nr) {
		add_element(pool, element);
	}

	pool->free(element, pool->pool_data);

	pthread_mutex_unlock(&pool->lock);

	pthread_cond_signal(&pool->has_free_elements);
}
